package com.cloudkinto.service.product.impl;

import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloudkinto.bo.product.ProductCategoryAddBo;
import com.cloudkinto.bo.product.ProductCategoryQueryBo;
import com.cloudkinto.bo.product.ProductCategoryUpdateBo;
import com.cloudkinto.common.ExcelExportTitleStyle;
import com.cloudkinto.common.constant.SysConstant;
import com.cloudkinto.common.exception.BizException;
import com.cloudkinto.common.utils.ExcelUtils;
import com.cloudkinto.common.web.PageResult;
import com.cloudkinto.dao.ProductCategoryDao;
import com.cloudkinto.dto.product.ProductCategoryExportDto;
import com.cloudkinto.dto.product.ProductCategoryImportDto;
import com.cloudkinto.entity.ProductCategoryDo;
import com.cloudkinto.entity.ProductDo;
import com.cloudkinto.service.product.ProductCategoryService;
import com.cloudkinto.service.product.ProductService;
import com.cloudkinto.vo.product.ProductCategoryTree;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 产品分类 service实现类
 * </p>
 *
 * @author long
 * @since 2024-10-29
 */
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class ProductCategoryServiceImpl extends ServiceImpl<ProductCategoryDao, ProductCategoryDo> implements ProductCategoryService {

    private final ProductService productService;

    @Override
    public void add(ProductCategoryAddBo bo) {
        ProductCategoryDo parent = null;
        if (Objects.nonNull(bo.getParentId())) {
            parent = baseMapper.selectOne(
                    Wrappers.<ProductCategoryDo>lambdaQuery().eq(ProductCategoryDo::getId, bo.getParentId())
            );
            if (Objects.isNull(parent)) {
                throw new BizException(SysConstant.Product_Category_Parent_Not_Exist);
            }
            if (parent.getTier() == 3) {
                throw new BizException(SysConstant.Product_Category_Max_Tier);
            }
        }
        ProductCategoryDo entityDo = new ProductCategoryDo();
        entityDo.setCategoryName(bo.getCategoryName());
        entityDo.setTier(Objects.isNull(parent) ? 1 : parent.getTier() + 1);
        entityDo.setParentId(Objects.isNull(parent) ? -1L : parent.getId());
        entityDo.setFullPathName(
                Objects.isNull(parent)
                        ? bo.getCategoryName()
                        : parent.getFullPathName() + "\\" + bo.getCategoryName()
        );
        baseMapper.insert(entityDo);
        entityDo.setFullPath(Objects.isNull(parent) ? String.valueOf(entityDo.getId()) : parent.getFullPath() + "," + entityDo.getId());
        baseMapper.updateById(entityDo);
    }

    @Override
    public void update(ProductCategoryUpdateBo bo) {
        ProductCategoryDo parent = null;
        if (Objects.nonNull(bo.getParentId())) {
            parent = baseMapper.selectOne(
                    Wrappers.<ProductCategoryDo>lambdaQuery().eq(ProductCategoryDo::getId, bo.getParentId())
            );
            if (Objects.isNull(parent)) {
                throw new BizException(SysConstant.Product_Category_Parent_Not_Exist);
            }
            //查看当前分类下有几级，避免超过三级
            long currentCategoryUnderHowMuchLabels = baseMapper.selectList(
                    Wrappers.<ProductCategoryDo>lambdaQuery()
                            .apply("find_in_set({0},full_path)", bo.getCategoryId())
            ).stream().map(ProductCategoryDo::getTier).distinct().count();
            if (parent.getTier() + currentCategoryUnderHowMuchLabels > 3 || parent.getTier() == 3) {
                throw new BizException(SysConstant.Product_Category_Max_Tier);
            }
        }
        ProductCategoryDo oldDo = baseMapper.selectById(bo.getCategoryId());
        //分类名联动修改
        if (!oldDo.getCategoryName().equals(bo.getCategoryName())) {
            List<ProductCategoryDo> productCategoryDoList = baseMapper.selectList(
                    Wrappers.<ProductCategoryDo>lambdaQuery()
                            .apply("find_in_set({0},full_path)", bo.getCategoryId())
            );
            for (ProductCategoryDo categoryDo : productCategoryDoList) {
                List<String> categoryNameList = Arrays.asList(categoryDo.getFullPathName().split("\\\\"));
                categoryNameList.set(
                        Arrays.asList(categoryDo.getFullPath().split(",")).indexOf(String.valueOf(bo.getCategoryId())),
                        bo.getCategoryName()
                );
                categoryDo.setFullPathName(String.join("\\", categoryNameList));
                baseMapper.updateById(categoryDo);
            }
        }
        ProductCategoryDo entityDo = new ProductCategoryDo();
        entityDo.setId(bo.getCategoryId());
        entityDo.setCategoryName(bo.getCategoryName());
        entityDo.setTier(Objects.isNull(parent) ? 1 : parent.getTier() + 1);
        entityDo.setParentId(Objects.isNull(parent) ? -1L : parent.getId());
        entityDo.setFullPathName(
                Objects.isNull(parent)
                        ? bo.getCategoryName()
                        : parent.getFullPathName() + "\\" + bo.getCategoryName()
        );
        entityDo.setFullPath(
                Objects.isNull(parent)
                        ? String.valueOf(bo.getCategoryId())
                        : parent.getFullPath() + "," + entityDo.getId()
        );
        baseMapper.updateById(entityDo);
    }

    @Override
    public int delete(Long id) {
        List<Long> needDeleteIds = baseMapper.selectList(
                        Wrappers.<ProductCategoryDo>lambdaQuery()
                                .select(ProductCategoryDo::getId, ProductCategoryDo::getFullPath)
                                .apply("find_in_set({0},full_path)", id)
                )
                .stream().map(ProductCategoryDo::getId)
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(needDeleteIds)) {
            return 0;
        }
        List<ProductDo> productBindCategoryList = productService.list(
                Wrappers.<ProductDo>lambdaQuery().in(ProductDo::getCategoryId, needDeleteIds)
        );
        if (CollUtil.isNotEmpty(productBindCategoryList)) {
            throw new BizException(SysConstant.Product_Category_Delete_Error);
        }
        return baseMapper.deleteBatchIds(needDeleteIds);
    }

    @Override
    public PageResult getListPage(ProductCategoryQueryBo bo) {
        LambdaQueryWrapper<ProductCategoryDo> wrapper = queryBuild(bo);
        IPage<ProductCategoryDo> page = new Page<>(bo.getCurrent(), bo.getPageSize());
        page = this.page(page, wrapper);
        return new PageResult<>(
                page.getRecords(),
                (int) page.getCurrent(),
                (int) page.getSize(),
                (int) page.getTotal(),
                (int) page.getPages()
        );
    }

    @Override
    public List<ProductCategoryTree> getCategoryTree(Long tenantId) {
        List<ProductCategoryDo> categoryDoList = baseMapper.selectList(
                Wrappers.<ProductCategoryDo>lambdaQuery()
                        .eq(ProductCategoryDo::getTenantId, tenantId)
        );
        List<ProductCategoryTree> result = new ArrayList<>();
        categoryDoList.stream().filter(i -> i.getTier().equals(1)).forEach(item -> {
            result.add(
                    ProductCategoryTree.builder()
                            .categoryId(item.getId())
                            .categoryName(item.getCategoryName())
                            .fullPath(item.getFullPath())
                            .fullPathName(item.getFullPathName())
                            .tier(item.getTier())
                            .createTime(item.getCreateTime())
                            .children(handlerChildren(categoryDoList, item.getId()))
                            .build()
            );
        });
        return result;
    }

    /**
     * 递归树
     */
    private List<ProductCategoryTree> handlerChildren(List<ProductCategoryDo> categoryDoList, Long parentId) {
        List<ProductCategoryTree> result = new ArrayList<>();
        categoryDoList.stream().filter(i -> i.getParentId().equals(parentId)).forEach(item ->
                result.add(
                        ProductCategoryTree.builder()
                                .categoryId(item.getId())
                                .categoryName(item.getCategoryName())
                                .parentId(parentId)
                                .fullPath(item.getFullPath())
                                .fullPathName(item.getFullPathName())
                                .tier(item.getTier())
                                .createTime(item.getCreateTime())
                                .children(
                                        item.getTier().equals(3)
                                                ? Collections.emptyList()
                                                : handlerChildren(categoryDoList, item.getId())
                                )
                                .build()
                ));
        return result;
    }

    @Override
    public void importCategory(MultipartFile file, Long tenantId) {
        List<ProductCategoryImportDto> importDtoList;
        try {
            importDtoList = ExcelUtils.importExcel(file, 1, 0, ProductCategoryImportDto.class);
        } catch (Exception e) {
            throw new BizException(SysConstant.Excel_Template_Error);
        }
        if (importDtoList.isEmpty()) {
            return;
        }
        //去重
        importDtoList = importDtoList.stream().distinct().collect(Collectors.toList());
        //分类名对应的id，一级分类key：一级分类名，二级的key：一级名*二级名
        Map<String, Long> categoryIdMap = new HashMap<>(importDtoList.size() * 2);
        //临时存储需要修改全路径的实体
        List<ProductCategoryDo> categoryTempList = new ArrayList<>();
        //处理一级分类
        List<String> oneLebalCategory = importDtoList.stream()
                .map(ProductCategoryImportDto::getOneLevelCategory)
                .distinct()
                .collect(Collectors.toList());
        oneLebalCategory.forEach(item -> {
            ProductCategoryDo exist = baseMapper.selectOne(
                    Wrappers.<ProductCategoryDo>lambdaQuery()
                            .select(
                                    ProductCategoryDo::getId,
                                    ProductCategoryDo::getTier,
                                    ProductCategoryDo::getCategoryName
                            )
                            .eq(ProductCategoryDo::getCategoryName, item)
                            .eq(ProductCategoryDo::getTier, 1)
            );
            if (Objects.nonNull(exist)) {
                categoryIdMap.put(item, (exist.getId()));
            } else {
                ProductCategoryDo categoryDo = new ProductCategoryDo();
                categoryDo.setCategoryName(item);
                categoryDo.setParentId(-1L);
                categoryDo.setFullPathName(item);
                categoryDo.setTier(1);
                categoryDo.setTenantId(tenantId);
                baseMapper.insert(categoryDo);
                categoryDo.setFullPath(String.valueOf(categoryDo.getId()));
                categoryTempList.add(categoryDo);
                categoryIdMap.put(item, categoryDo.getId());
            }
        });
        //处理二三级分类
        importDtoList.forEach(item -> {
            ProductCategoryDo twoExist = baseMapper.selectOne(
                    Wrappers.<ProductCategoryDo>lambdaQuery()
                            .select(
                                    ProductCategoryDo::getId,
                                    ProductCategoryDo::getCategoryName,
                                    ProductCategoryDo::getParentId,
                                    ProductCategoryDo::getTier
                            )
                            .eq(ProductCategoryDo::getCategoryName, item.getTwoLevelCategory())
                            .eq(ProductCategoryDo::getParentId, categoryIdMap.get(item.getOneLevelCategory()))
                            .eq(ProductCategoryDo::getTier, 2)
            );
            if (Objects.isNull(twoExist)) {
                ProductCategoryDo categoryDo = new ProductCategoryDo();
                categoryDo.setCategoryName(item.getTwoLevelCategory());
                categoryDo.setParentId(categoryIdMap.get(item.getOneLevelCategory()));
                categoryDo.setFullPath(String.valueOf(categoryIdMap.get(item.getOneLevelCategory())));
                categoryDo.setFullPathName(item.getOneLevelCategory() + "\\" + item.getTwoLevelCategory());
                categoryDo.setTier(2);
                categoryDo.setTenantId(tenantId);
                baseMapper.insert(categoryDo);
                categoryDo.setFullPath(categoryDo.getFullPath() + "," + categoryDo.getId());
                categoryTempList.add(categoryDo);
                categoryIdMap.put(item.getOneLevelCategory() + "*" + item.getTwoLevelCategory(), categoryDo.getId());
            } else {
                categoryIdMap.put(item.getOneLevelCategory() + "*" + item.getTwoLevelCategory(), twoExist.getId());
            }
            ProductCategoryDo threeExist = baseMapper.selectOne(
                    Wrappers.<ProductCategoryDo>lambdaQuery()
                            .select(
                                    ProductCategoryDo::getId,
                                    ProductCategoryDo::getCategoryName,
                                    ProductCategoryDo::getParentId,
                                    ProductCategoryDo::getTier
                            )
                            .eq(ProductCategoryDo::getCategoryName, item.getThreeLevelCategory())
                            .eq(ProductCategoryDo::getParentId, categoryIdMap.get(item.getOneLevelCategory() + "*" + item.getTwoLevelCategory()))
                            .eq(ProductCategoryDo::getTier, 3)
            );
            if (Objects.isNull(threeExist)) {
                ProductCategoryDo categoryDo = new ProductCategoryDo();
                categoryDo.setCategoryName(item.getThreeLevelCategory());
                categoryDo.setParentId(categoryIdMap.get(item.getOneLevelCategory() + "*" + item.getTwoLevelCategory()));
                categoryDo.setFullPath(
                        categoryIdMap.get(item.getOneLevelCategory()) + "," +
                                categoryIdMap.get(item.getOneLevelCategory() + "*" + item.getTwoLevelCategory())
                );
                categoryDo.setFullPathName(item.getOneLevelCategory() + "\\" + item.getTwoLevelCategory() + "\\" + item.getThreeLevelCategory());
                categoryDo.setTier(3);
                categoryDo.setTenantId(tenantId);
                baseMapper.insert(categoryDo);
                categoryDo.setFullPath(categoryDo.getFullPath() + "," + categoryDo.getId());
                categoryTempList.add(categoryDo);
            }
        });
        //批量修改分类的全路径id
        if (CollUtil.isNotEmpty(categoryTempList)) {
            this.saveOrUpdateBatch(categoryTempList);
        }
    }

    @Override
    public void exportCategory(List<Long> ids, Long tenantId, HttpServletResponse response) throws IOException {
        //Excel文件导出配置
        ExportParams excelParam = new ExportParams();
        excelParam.setType(ExcelType.HSSF);
        excelParam.setStyle(ExcelExportTitleStyle.class);
        List<ProductCategoryExportDto> exportDtoList = new ArrayList<>();
        if (CollUtil.isEmpty(ids)) {
            //全局导出
            List<ProductCategoryTree> categoryTree = this.getCategoryTree(tenantId);
            categoryTree.forEach(one -> {
                if (one.getChildren().isEmpty()) {
                    exportDtoList.add(
                            ProductCategoryExportDto.builder()
                                    .oneLevelCategory(one.getCategoryName())
                                    .createTime(one.getCreateTime())
                                    .build()
                    );
                } else {
                    one.getChildren().forEach(two -> {
                        if (two.getChildren().isEmpty()) {
                            exportDtoList.add(
                                    ProductCategoryExportDto.builder()
                                            .oneLevelCategory(one.getCategoryName())
                                            .twoLevelCategory(two.getCategoryName())
                                            .createTime(two.getCreateTime())
                                            .build()
                            );
                        } else {
                            two.getChildren().forEach(three -> {
                                exportDtoList.add(
                                        ProductCategoryExportDto.builder()
                                                .oneLevelCategory(one.getCategoryName())
                                                .twoLevelCategory(two.getCategoryName())
                                                .threeLevelCategory(three.getCategoryName())
                                                .createTime(three.getCreateTime())
                                                .build()
                                );
                            });
                        }
                    });
                }
            });
        } else {
            //按勾选导出
            List<ProductCategoryDo> categoryDoList = baseMapper.selectList(
                    Wrappers.<ProductCategoryDo>lambdaQuery()
                            .select(
                                    ProductCategoryDo::getId,
                                    ProductCategoryDo::getFullPathName,
                                    ProductCategoryDo::getCreateTime
                            )
                            .in(ProductCategoryDo::getId, ids)
            );
            categoryDoList.forEach(item -> {
                String[] categoryNameArr = item.getFullPathName().split("\\\\");
                exportDtoList.add(
                        ProductCategoryExportDto.builder()
                                .oneLevelCategory(categoryNameArr[0])
                                .twoLevelCategory(categoryNameArr.length == 2 ? categoryNameArr[1] : null)
                                .threeLevelCategory(categoryNameArr.length == 3 ? categoryNameArr[2] : null)
                                .createTime(item.getCreateTime())
                                .build()
                );

            });
        }
        ExcelUtils.exportExcel(
                exportDtoList,
                ProductCategoryExportDto.class,
                "ProductCategory_Export_" + DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN),
                excelParam,
                response,
                "xls"
        );
    }

    private LambdaQueryWrapper<ProductCategoryDo> queryBuild(ProductCategoryQueryBo bo) {
        LambdaQueryWrapper<ProductCategoryDo> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Objects.nonNull(bo.getTenantId()), ProductCategoryDo::getTenantId, bo.getTenantId());
        lqw.apply(Objects.nonNull(bo.getCategoryId()), "find_in_set({0},full_path)", bo.getCategoryId());
        lqw.and(
                Objects.nonNull(bo.getTier()),
                wrapper -> wrapper.eq(ProductCategoryDo::getTier, bo.getTier())
                        .or().eq(ProductCategoryDo::getTier, Objects.nonNull(bo.getTier()) ? bo.getTier() + 1 : null)
        );
        //初始化分页列表时,默认查询一级
        lqw.eq(Objects.isNull(bo.getCategoryId()) && Objects.isNull(bo.getTier()), ProductCategoryDo::getTier, 1);
        lqw.last(
                StrUtil.isNotBlank(bo.getSortName()),
                "order by " + bo.getSortName() + " " + bo.getSortOrder()
        );
        return lqw;
    }


}
